//===-- Symbols.cpp ---------------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "lldb/Host/Symbols.h"

// C Includes
#include <dirent.h>
#include <pwd.h>
#include "lldb/Utility/SafeMachO.h"

// C++ Includes
// Other libraries and framework includes
#include <CoreFoundation/CoreFoundation.h>

// Project includes
#include "lldb/Core/ArchSpec.h"
#include "lldb/Core/DataBuffer.h"
#include "lldb/Core/DataExtractor.h"
#include "lldb/Core/Log.h"
#include "lldb/Core/Module.h"
#include "lldb/Core/ModuleSpec.h"
#include "lldb/Core/StreamString.h"
#include "lldb/Core/Timer.h"
#include "lldb/Core/UUID.h"
#include "lldb/Host/Endian.h"
#include "lldb/Host/Host.h"
#include "lldb/Symbol/ObjectFile.h"
#include "lldb/Utility/CleanUp.h"
#include "Host/macosx/cfcpp/CFCBundle.h"
#include "Host/macosx/cfcpp/CFCData.h"
#include "Host/macosx/cfcpp/CFCReleaser.h"
#include "Host/macosx/cfcpp/CFCString.h"
#include "mach/machine.h"

using namespace lldb;
using namespace lldb_private;
using namespace llvm::MachO;

#if !defined (__arm__) && !defined (__arm64__) && !defined (__aarch64__) // No DebugSymbols on the iOS devices
extern "C" {

CFURLRef DBGCopyFullDSYMURLForUUID (CFUUIDRef uuid, CFURLRef exec_url);
CFDictionaryRef DBGCopyDSYMPropertyLists (CFURLRef dsym_url);

}
#endif

int
LocateMacOSXFilesUsingDebugSymbols
(
    const ModuleSpec &module_spec,
    ModuleSpec &return_module_spec
)
{
    return_module_spec = module_spec;
    return_module_spec.GetFileSpec().Clear();
    return_module_spec.GetSymbolFileSpec().Clear();

    int items_found = 0;

#if !defined (__arm__) && !defined (__arm64__) && !defined (__aarch64__) // No DebugSymbols on the iOS devices

    const UUID *uuid = module_spec.GetUUIDPtr();
    const ArchSpec *arch = module_spec.GetArchitecturePtr();

    if (uuid && uuid->IsValid())
    {
        // Try and locate the dSYM file using DebugSymbols first
        const UInt8 *module_uuid = (const UInt8 *)uuid->GetBytes();
        if (module_uuid != NULL)
        {
            CFCReleaser<CFUUIDRef> module_uuid_ref(::CFUUIDCreateWithBytes (NULL,
                                                                            module_uuid[0],
                                                                            module_uuid[1],
                                                                            module_uuid[2],
                                                                            module_uuid[3],
                                                                            module_uuid[4],
                                                                            module_uuid[5],
                                                                            module_uuid[6],
                                                                            module_uuid[7],
                                                                            module_uuid[8],
                                                                            module_uuid[9],
                                                                            module_uuid[10],
                                                                            module_uuid[11],
                                                                            module_uuid[12],
                                                                            module_uuid[13],
                                                                            module_uuid[14],
                                                                            module_uuid[15]));

            if (module_uuid_ref.get())
            {
                CFCReleaser<CFURLRef> exec_url;
                const FileSpec *exec_fspec = module_spec.GetFileSpecPtr();
                Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST);
                if (exec_fspec)
                {
                    char exec_cf_path[PATH_MAX];
                    if (exec_fspec->GetPath(exec_cf_path, sizeof(exec_cf_path)))
                        exec_url.reset(::CFURLCreateFromFileSystemRepresentation (NULL,
                                                                                  (const UInt8 *)exec_cf_path,
                                                                                  strlen(exec_cf_path),
                                                                                  FALSE));
                }

                CFCReleaser<CFURLRef> dsym_url (::DBGCopyFullDSYMURLForUUID(module_uuid_ref.get(), exec_url.get()));
                char path[PATH_MAX];

                if (dsym_url.get())
                {
                    if (::CFURLGetFileSystemRepresentation (dsym_url.get(), true, (UInt8*)path, sizeof(path)-1))
                    {
                        if (log)
                        {
                            log->Printf ("DebugSymbols framework returned dSYM path of %s for UUID %s -- looking for the dSYM", path, uuid->GetAsString().c_str());
                        }
                        FileSpec dsym_filespec(path, path[0] == '~');

                        if (dsym_filespec.GetFileType () == FileSpec::eFileTypeDirectory)
                        {
                            dsym_filespec = Symbols::FindSymbolFileInBundle (dsym_filespec, uuid, arch);
                            ++items_found;
                        }
                        else
                        {
                            ++items_found;
                        }
                        return_module_spec.GetSymbolFileSpec() = dsym_filespec;
                    }

                    bool success = false;
                    if (log)
                    {
                        if (::CFURLGetFileSystemRepresentation (dsym_url.get(), true, (UInt8*)path, sizeof(path)-1))
                        {
                            log->Printf ("DebugSymbols framework returned dSYM path of %s for UUID %s -- looking for an exec file", path, uuid->GetAsString().c_str());
                        }

                    }

                    CFCReleaser<CFDictionaryRef> dict(::DBGCopyDSYMPropertyLists (dsym_url.get()));
                    CFDictionaryRef uuid_dict = NULL;
                    if (dict.get())
                    {
                        CFCString uuid_cfstr (uuid->GetAsString().c_str());
                        uuid_dict = static_cast<CFDictionaryRef>(::CFDictionaryGetValue (dict.get(), uuid_cfstr.get()));
                    }
                    if (uuid_dict)
                    {
                        CFStringRef exec_cf_path = static_cast<CFStringRef>(::CFDictionaryGetValue (uuid_dict, CFSTR("DBGSymbolRichExecutable")));
                        if (exec_cf_path && ::CFStringGetFileSystemRepresentation (exec_cf_path, path, sizeof(path)))
                        {
                            if (log)
                            {
                                log->Printf ("plist bundle has exec path of %s for UUID %s", path, uuid->GetAsString().c_str());
                            }
                            ++items_found;
                            FileSpec exec_filespec (path, path[0] == '~');
                            if (exec_filespec.Exists())
                            {
                                success = true;
                                return_module_spec.GetFileSpec() = exec_filespec;
                            }
                        }
                    }

                    if (!success)
                    {
                        // No dictionary, check near the dSYM bundle for an executable that matches...
                        if (::CFURLGetFileSystemRepresentation (dsym_url.get(), true, (UInt8*)path, sizeof(path)-1))
                        {
                            char *dsym_extension_pos = ::strstr (path, ".dSYM");
                            if (dsym_extension_pos)
                            {
                                *dsym_extension_pos = '\0';
                                if (log)
                                {
                                    log->Printf ("Looking for executable binary next to dSYM bundle with name with name %s", path);
                                }
                                FileSpec file_spec (path, true);
                                ModuleSpecList module_specs;
                                ModuleSpec matched_module_spec;
                                switch (file_spec.GetFileType())
                                {
                                    case FileSpec::eFileTypeDirectory:  // Bundle directory?
                                        {
                                            CFCBundle bundle (path);
                                            CFCReleaser<CFURLRef> bundle_exe_url (bundle.CopyExecutableURL ());
                                            if (bundle_exe_url.get())
                                            {
                                                if (::CFURLGetFileSystemRepresentation (bundle_exe_url.get(), true, (UInt8*)path, sizeof(path)-1))
                                                {
                                                    FileSpec bundle_exe_file_spec (path, true);
                                                    if (ObjectFile::GetModuleSpecifications(bundle_exe_file_spec, 0, 0, module_specs) &&
                                                        module_specs.FindMatchingModuleSpec(module_spec, matched_module_spec))

                                                    {
                                                        ++items_found;
                                                        return_module_spec.GetFileSpec() = bundle_exe_file_spec;
                                                        if (log)
                                                        {
                                                            log->Printf ("Executable binary %s next to dSYM is compatible; using", path);
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                        break;

                                    case FileSpec::eFileTypePipe:       // Forget pipes
                                    case FileSpec::eFileTypeSocket:     // We can't process socket files
                                    case FileSpec::eFileTypeInvalid:    // File doesn't exist...
                                        break;

                                    case FileSpec::eFileTypeUnknown:
                                    case FileSpec::eFileTypeRegular:
                                    case FileSpec::eFileTypeSymbolicLink:
                                    case FileSpec::eFileTypeOther:
                                        if (ObjectFile::GetModuleSpecifications(file_spec, 0, 0, module_specs) &&
                                            module_specs.FindMatchingModuleSpec(module_spec, matched_module_spec))

                                        {
                                            ++items_found;
                                            return_module_spec.GetFileSpec() = file_spec;
                                            if (log)
                                            {
                                                log->Printf ("Executable binary %s next to dSYM is compatible; using", path);
                                            }
                                        }
                                        break;
                                }
                            }
                        }
                    }
                }
            }
        }
    }
#endif // #if !defined (__arm__) && !defined (__arm64__) && !defined (__aarch64__)

    return items_found;
}

FileSpec
Symbols::FindSymbolFileInBundle (const FileSpec& dsym_bundle_fspec,
                                 const lldb_private::UUID *uuid,
                                 const ArchSpec *arch)
{
    char path[PATH_MAX];

    FileSpec dsym_fspec;

    if (dsym_bundle_fspec.GetPath(path, sizeof(path)))
    {
        ::strncat (path, "/Contents/Resources/DWARF", sizeof(path) - strlen(path) - 1);

        lldb_utility::CleanUp <DIR *, int> dirp (opendir(path), NULL, closedir);
        if (dirp.is_valid())
        {
            dsym_fspec.GetDirectory().SetCString(path);
            struct dirent* dp;
            while ((dp = readdir(dirp.get())) != NULL)
            {
                // Only search directories
                if (dp->d_type == DT_DIR || dp->d_type == DT_UNKNOWN)
                {
                    if (dp->d_namlen == 1 && dp->d_name[0] == '.')
                        continue;

                    if (dp->d_namlen == 2 && dp->d_name[0] == '.' && dp->d_name[1] == '.')
                        continue;
                }

                if (dp->d_type == DT_REG || dp->d_type == DT_UNKNOWN)
                {
                    dsym_fspec.GetFilename().SetCString(dp->d_name);
                    ModuleSpecList module_specs;
                    if (ObjectFile::GetModuleSpecifications(dsym_fspec, 0, 0, module_specs))
                    {
                        ModuleSpec spec;
                        for (size_t i = 0; i < module_specs.GetSize(); ++i)
                        {
                            assert(module_specs.GetModuleSpecAtIndex(i, spec));
                            if ((uuid == NULL || (spec.GetUUIDPtr() && spec.GetUUID() == *uuid)) &&
                                (arch == NULL || (spec.GetArchitecturePtr() && spec.GetArchitecture().IsCompatibleMatch(*arch))))
                            {
                                return dsym_fspec;
                            }
                        }
                    }
                }
            }
        }
    }
    dsym_fspec.Clear();
    return dsym_fspec;
}

static bool
GetModuleSpecInfoFromUUIDDictionary (CFDictionaryRef uuid_dict, ModuleSpec &module_spec)
{
    Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST);
    bool success = false;
    if (uuid_dict != NULL && CFGetTypeID (uuid_dict) == CFDictionaryGetTypeID ())
    {
        std::string str;
        CFStringRef cf_str;
        
        cf_str = (CFStringRef)CFDictionaryGetValue ((CFDictionaryRef) uuid_dict, CFSTR("DBGSymbolRichExecutable"));
        if (cf_str && CFGetTypeID (cf_str) == CFStringGetTypeID ())
        {
            if (CFCString::FileSystemRepresentation(cf_str, str))
            {
                module_spec.GetFileSpec().SetFile (str.c_str(), true);
                if (log)
                {
                    log->Printf ("From dsymForUUID plist: Symbol rich executable is at '%s'", str.c_str());
                }
            }
        }
        
        cf_str = (CFStringRef)CFDictionaryGetValue ((CFDictionaryRef) uuid_dict, CFSTR("DBGDSYMPath"));
        if (cf_str && CFGetTypeID (cf_str) == CFStringGetTypeID ())
        {
            if (CFCString::FileSystemRepresentation(cf_str, str))
            {
                module_spec.GetSymbolFileSpec().SetFile (str.c_str(), true);
                success = true;
                if (log)
                {
                    log->Printf ("From dsymForUUID plist: dSYM is at '%s'", str.c_str());
                }
            }
        }
        
        cf_str = (CFStringRef)CFDictionaryGetValue ((CFDictionaryRef) uuid_dict, CFSTR("DBGArchitecture"));
        if (cf_str && CFGetTypeID (cf_str) == CFStringGetTypeID ())
        {
            if (CFCString::FileSystemRepresentation(cf_str, str))
                module_spec.GetArchitecture().SetTriple(str.c_str());
        }

        std::string DBGBuildSourcePath;
        std::string DBGSourcePath;

        cf_str = (CFStringRef)CFDictionaryGetValue ((CFDictionaryRef) uuid_dict, CFSTR("DBGBuildSourcePath"));
        if (cf_str && CFGetTypeID (cf_str) == CFStringGetTypeID ())
        {
            CFCString::FileSystemRepresentation(cf_str, DBGBuildSourcePath);
        }

        cf_str = (CFStringRef)CFDictionaryGetValue ((CFDictionaryRef) uuid_dict, CFSTR("DBGSourcePath"));
        if (cf_str && CFGetTypeID (cf_str) == CFStringGetTypeID ())
        {
            CFCString::FileSystemRepresentation(cf_str, DBGSourcePath);
        }
        
        if (!DBGBuildSourcePath.empty() && !DBGSourcePath.empty())
        {
            module_spec.GetSourceMappingList().Append (ConstString(DBGBuildSourcePath.c_str()), ConstString(DBGSourcePath.c_str()), true);
        }
    }
    return success;
}


bool
Symbols::DownloadObjectAndSymbolFile (ModuleSpec &module_spec, bool force_lookup)
{
    bool success = false;
    const UUID *uuid_ptr = module_spec.GetUUIDPtr();
    const FileSpec *file_spec_ptr = module_spec.GetFileSpecPtr();

    // It's expensive to check for the DBGShellCommands defaults setting, only do it once per
    // lldb run and cache the result.  
    static bool g_have_checked_for_dbgshell_command = false;
    static const char *g_dbgshell_command = NULL;
    if (g_have_checked_for_dbgshell_command == false)
    {
        g_have_checked_for_dbgshell_command = true;
        CFTypeRef defaults_setting = CFPreferencesCopyAppValue (CFSTR ("DBGShellCommands"), CFSTR ("com.apple.DebugSymbols"));
        if (defaults_setting && CFGetTypeID (defaults_setting) == CFStringGetTypeID())
        { 
            char cstr_buf[PATH_MAX];
            if (CFStringGetCString ((CFStringRef) defaults_setting, cstr_buf, sizeof (cstr_buf), kCFStringEncodingUTF8))
            {
                g_dbgshell_command = strdup (cstr_buf);  // this malloc'ed memory will never be freed
            }
        }
        if (defaults_setting)
        {
            CFRelease (defaults_setting);
        }
    }

    // When g_dbgshell_command is NULL, the user has not enabled the use of an external program
    // to find the symbols, don't run it for them.
    if (force_lookup == false && g_dbgshell_command == NULL)
    {
        return false;
    }

    if (uuid_ptr || (file_spec_ptr && file_spec_ptr->Exists()))
    {
        static bool g_located_dsym_for_uuid_exe = false;
        static bool g_dsym_for_uuid_exe_exists = false;
        static char g_dsym_for_uuid_exe_path[PATH_MAX];
        if (!g_located_dsym_for_uuid_exe)
        {
            g_located_dsym_for_uuid_exe = true;
            const char *dsym_for_uuid_exe_path_cstr = getenv("LLDB_APPLE_DSYMFORUUID_EXECUTABLE");
            FileSpec dsym_for_uuid_exe_spec;
            if (dsym_for_uuid_exe_path_cstr)
            {
                dsym_for_uuid_exe_spec.SetFile(dsym_for_uuid_exe_path_cstr, true);
                g_dsym_for_uuid_exe_exists = dsym_for_uuid_exe_spec.Exists();
            }
            
            if (!g_dsym_for_uuid_exe_exists)
            {
                dsym_for_uuid_exe_spec.SetFile("/usr/local/bin/dsymForUUID", false);
                g_dsym_for_uuid_exe_exists = dsym_for_uuid_exe_spec.Exists();
                if (!g_dsym_for_uuid_exe_exists)
                {
                    long bufsize;
                    if ((bufsize = sysconf(_SC_GETPW_R_SIZE_MAX)) != -1)
                    {
                        char buffer[bufsize];
                        struct passwd pwd;
                        struct passwd *tilde_rc = NULL;
                        // we are a library so we need to use the reentrant version of getpwnam()
                        if (getpwnam_r ("rc", &pwd, buffer, bufsize, &tilde_rc) == 0 
                            && tilde_rc 
                            && tilde_rc->pw_dir)
                        {
                            std::string dsymforuuid_path(tilde_rc->pw_dir);
                            dsymforuuid_path += "/bin/dsymForUUID";
                            dsym_for_uuid_exe_spec.SetFile(dsymforuuid_path.c_str(), false);
                            g_dsym_for_uuid_exe_exists = dsym_for_uuid_exe_spec.Exists();
                        }
                    }
                }
            }
            if (!g_dsym_for_uuid_exe_exists && g_dbgshell_command != NULL)
            {
                dsym_for_uuid_exe_spec.SetFile(g_dbgshell_command, true);
                g_dsym_for_uuid_exe_exists = dsym_for_uuid_exe_spec.Exists();
            }

            if (g_dsym_for_uuid_exe_exists)
                dsym_for_uuid_exe_spec.GetPath (g_dsym_for_uuid_exe_path, sizeof(g_dsym_for_uuid_exe_path));
        }
        if (g_dsym_for_uuid_exe_exists)
        {
            std::string uuid_str;
            char file_path[PATH_MAX];
            file_path[0] = '\0';

            if (uuid_ptr)
                uuid_str = uuid_ptr->GetAsString();

            if (file_spec_ptr)
                file_spec_ptr->GetPath(file_path, sizeof(file_path));
            
            StreamString command;
            if (!uuid_str.empty())
                command.Printf("%s --ignoreNegativeCache --copyExecutable --databases bursar.apple.com,uuidsymmap.apple.com %s", g_dsym_for_uuid_exe_path, uuid_str.c_str());
            else if (file_path[0] != '\0')
                command.Printf("%s --ignoreNegativeCache --copyExecutable --databases bursar.apple.com,uuidsymmap.apple.com %s", g_dsym_for_uuid_exe_path, file_path);
            
            if (!command.GetString().empty())
            {
                Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST);
                int exit_status = -1;
                int signo = -1;
                std::string command_output;
                if (log)
                {
                    if (!uuid_str.empty())
                        log->Printf("Calling %s with UUID %s to find dSYM", g_dsym_for_uuid_exe_path, uuid_str.c_str());
                    else if (file_path[0] != '\0')
                        log->Printf("Calling %s with file %s to find dSYM", g_dsym_for_uuid_exe_path, file_path);
                }
                Error error = Host::RunShellCommand (command.GetData(),
                                                     NULL,              // current working directory
                                                     &exit_status,      // Exit status
                                                     &signo,            // Signal int *
                                                     &command_output,   // Command output
                                                     30,                // Large timeout to allow for long dsym download times
                                                     false);            // Don't run in a shell (we don't need shell expansion)
                if (error.Success() && exit_status == 0 && !command_output.empty())
                {
                    CFCData data (CFDataCreateWithBytesNoCopy (NULL,
                                                               (const UInt8 *)command_output.data(),
                                                               command_output.size(),
                                                               kCFAllocatorNull));
                    
                    CFCReleaser<CFDictionaryRef> plist((CFDictionaryRef)::CFPropertyListCreateFromXMLData (NULL, data.get(), kCFPropertyListImmutable, NULL));
                    
                    if (plist.get() && CFGetTypeID (plist.get()) == CFDictionaryGetTypeID ())
                    {
                        if (!uuid_str.empty())
                        {
                            CFCString uuid_cfstr(uuid_str.c_str());
                            CFDictionaryRef uuid_dict = (CFDictionaryRef)CFDictionaryGetValue (plist.get(), uuid_cfstr.get());
                            success = GetModuleSpecInfoFromUUIDDictionary (uuid_dict, module_spec);
                        }
                        else
                        {
                            const CFIndex num_values = ::CFDictionaryGetCount(plist.get());
                            if (num_values > 0)
                            {
                                std::vector<CFStringRef> keys (num_values, NULL);
                                std::vector<CFDictionaryRef> values (num_values, NULL);
                                ::CFDictionaryGetKeysAndValues(plist.get(), NULL, (const void **)&values[0]);
                                if (num_values == 1)
                                {
                                    return GetModuleSpecInfoFromUUIDDictionary (values[0], module_spec);
                                }
                                else
                                {
                                    for (CFIndex i=0; i<num_values; ++i)
                                    {
                                        ModuleSpec curr_module_spec;
                                        if (GetModuleSpecInfoFromUUIDDictionary (values[i], curr_module_spec))
                                        {
                                            if (module_spec.GetArchitecture().IsCompatibleMatch(curr_module_spec.GetArchitecture()))
                                            {
                                                module_spec = curr_module_spec;
                                                return true;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                else
                {
                    if (log)
                    {
                        if (!uuid_str.empty())
                            log->Printf("Called %s on %s, no matches", g_dsym_for_uuid_exe_path, uuid_str.c_str());
                        else if (file_path[0] != '\0')
                            log->Printf("Called %s on %s, no matches", g_dsym_for_uuid_exe_path, file_path);
                    }
                }
            }
        }
    }
    return success;
}

